#include<iostream>
#include "Ast.hh"
#include "Error.hh"

extern Error *error;
extern ClassEntity *objectclass;

bool inMethod;
bool inConstructor;
ClassEntity *currClass;
MethodEntity *currMethod;
ConstructorEntity *currConstructor;

#define PUBLIC 		true
#define PRIVATE 	false

void initialize_typechecker() {
  // initialize any needed variables here...
	inMethod = false;
	inConstructor = false;
	currClass = NULL;
	currMethod = NULL;
	currConstructor = NULL;
}


void ClassEntity::typecheck() {
	currClass = this;
	list<Entity*>*members = this->class_members();
	list<Entity*>::iterator i;
//	cout << "In class"<<endl;
	for (i = members->begin(); i != members->end(); i++){
		Kind kind = (*i)->kind();
		if (kind == METHOD_ENTITY)
			((MethodEntity *)(*i))->typecheck();
		else if (kind == CONSTRUCTOR_ENTITY)
			((ConstructorEntity *)(*i))->typecheck();
	}
}

void MethodEntity::typecheck() {
	Statement *statement = this->method_body();
//	cout << "in method: "<<this->name()<<endl;
	inMethod = true;
	currMethod = this;
	((BlockStatement *)statement)->typecheck();
	currMethod = NULL;
	inMethod = false;
}

void ConstructorEntity::typecheck() {
	Statement *statement = this->constructor_body();
//	cout << "in constructor"<<endl;
	inConstructor = true;
	currConstructor = this;
	((BlockStatement *)statement)->typecheck();
	currConstructor = NULL;
	inConstructor = false;
}

// Typecheck method for IfStatement
void IfStatement::typecheck() {
	Type *type = expr_->typeinfer();
	TypeKind kind = type->kind();

	if (BOOLEAN_TYPE == kind) {
		thenpart_->typecheck();
		elsepart_->typecheck();
	}
	else if (ERROR_TYPE != kind)
		error->type_error(this->lineno(), "Wrong expression type in if", type, new BooleanType());
}

// Typecheck method for WhileStatement
void WhileStatement::typecheck() {
	Type *type = expr_->typeinfer();

	if (BOOLEAN_TYPE == (type->kind()))
		body_->typecheck();
	else if (ERROR_TYPE != (type->kind()))
		error->type_error(this->lineno(), "Wrong expression type in while", type, new BooleanType());
}

// Typecheck method for ForStatement
void ForStatement::typecheck() {
	Type *type = guard_->typeinfer();

 	if (BOOLEAN_TYPE == type->kind()) {
		init_->typecheck();
		update_->typecheck();
		body_->typecheck();
	}
	else if (ERROR_TYPE != (type->kind()))
		error->type_error(this->lineno(), "wrong guard type in for statement", type, new BooleanType());
}
 

// Typecheck method for ReturnStatement
void ReturnStatement::typecheck() {
	/* can have Return statement only in a Method */
	if ((false == inConstructor) && (true == inMethod)) {
		Type *type = expr_->typeinfer();
		
		if ((ERROR_TYPE != type->kind()) || (ERROR_TYPE != currMethod->return_type()->kind())) {
			if (!(type->isSubtypeOf(currMethod->return_type())))
				error->type_error(this->lineno(), "Return type incompatible with the declared type type", type, currMethod->return_type());
		}
	}
}


// Typecheck method for BlockStatement
void BlockStatement::typecheck() {
	list<Statement*>*statements = this->stmt_list();
	list<Statement*>::iterator i;
	for (i=statements->begin(); i!=statements->end(); i++){
		(*i)->typecheck();
	}
}

// Typecheck method for ExprStatement
void ExprStatement::typecheck() {
	Expression *expr = this->expr();
	expr_->typeinfer();
}



// Typecheck method for DeclStatement
void DeclStatement::typecheck() {
}



// Typecheck method for BreakStatement
void BreakStatement::typecheck() {
//	cout<<"found break"<<endl;
}



// Typecheck method for ContinueStatement
void ContinueStatement::typecheck() {
//	cout<<"found continue"<<endl;
}



// Typecheck method for SkipStatement
void SkipStatement::typecheck() {
//	cout<<"found skip"<<endl;
}



// Typeinfer method for BinaryExpression
Type* BinaryExpression::typeinfer() {
	Expression *lhs = this->lhs();
	Expression *rhs = this->rhs();
	Type *lhstype = lhs->typeinfer();
	Type *rhstype = rhs->typeinfer();
	TypeKind lhskind = lhstype->kind();
	TypeKind rhskind = rhstype->kind();
	BinaryOperator op = this->binary_operator();
	Type *type = NULL;

	if (ERROR_TYPE == lhskind)
		return lhstype;
	if (ERROR_TYPE == rhskind)
		return rhstype;

//	cout<<"lhs: "<<lhskind <<" rhs: "<<rhskind <<endl;
	switch (op) {
		case ADD:
		case SUB:
		case MUL:
		case DIV:
			if ((INT_TYPE != rhskind) && (FLOAT_TYPE != rhskind)) {
				error->type_error(this->lineno(), "Int/Float Type expected", rhstype);
				type = new ErrorType();
			}
			else if ((INT_TYPE != lhskind) && (FLOAT_TYPE != lhskind)){
				error->type_error(this->lineno(), "Int/Float type expected", lhstype);
				type = new ErrorType();
			}
			else if ((INT_TYPE == rhskind) && (INT_TYPE == lhskind))
				type = new IntType();
			else
				type = new FloatType();
			break;

		case LT:
		case LEQ:
		case GT:
		case GEQ:
			if (((INT_TYPE == rhskind) && (INT_TYPE == lhskind)) ||
					((FLOAT_TYPE == rhskind) && (FLOAT_TYPE == lhskind)))
				type = new BooleanType();
			else {
				error->type_error(this->lineno(), "Invalid Arguments to relational operator", rhstype, lhstype);
				type = new ErrorType();
			}
			break;

		case EQ:
		case NEQ:
			if (rhstype->isSubtypeOf(lhstype) ||
					lhstype->isSubtypeOf(rhstype))
				type = new BooleanType();
			else {
				error->type_error(this->lineno(), "Invalid Arguments to relational operator", rhstype, lhstype);
				type = new ErrorType();
			}
			break;

		case AND:
		case OR:
			if (BOOLEAN_TYPE != lhskind) {
				error->type_error(this->lineno(), "Invalid lhs", lhstype, new BooleanType());
				type = new ErrorType();
			}
			else if (BOOLEAN_TYPE != rhskind){
				error->type_error(this->lineno(), "Invalid rhs", rhstype, new BooleanType());
				type = new ErrorType();
			}
			else {
				type = new BooleanType();
			}
			break;

		default:
			;
	}
	return type;
}



// Typeinfer method for AssignExpression
Type* AssignExpression::typeinfer() {
	Type *lhstype = lhs_->typeinfer();
	Type *rhstype = rhs_->typeinfer();

	if(ERROR_TYPE == lhstype->kind())
		return lhstype;
	if(ERROR_TYPE == rhstype->kind())
		return rhstype;

	if (rhstype->isSubtypeOf(lhstype))
		return rhstype;
	else {
		error->type_error(this->lineno(), "Incompatible Types used in Assignment", lhstype, rhstype);
		return (new ErrorType());
	}
}



// Typeinfer method for ArrayAccess
Type* ArrayAccess::typeinfer() {
	Expression *base = this->base();
	Expression *index = this->idx();
	Type *basetype = base->typeinfer();
	Type *indextype = index->typeinfer();
	TypeKind basekind = basetype->kind();
	TypeKind indexkind = indextype->kind();

	if (ERROR_TYPE == basekind) 
		return basetype;
	if (ERROR_TYPE == indexkind)
		return indextype;

	if (ARRAY_TYPE != basekind) {
		error->type_error(this->lineno(), "Invalid Array base type", basetype);
		return (new ErrorType());
	}
	else if (INT_TYPE != indexkind) {
		error->type_error(this->lineno(), "invalid array index type", indextype, new IntType());
		return (new ErrorType());
	}
	else {
		return ((ArrayType *)basetype)->elementtype();
	}
}


// Typeinfer method for FieldAccess:
Type* FieldAccess::typeinfer() {
	char *fieldName = name_;
	Type *baseType = base_->typeinfer();
	TypeKind kind = baseType->kind();

	ClassEntity *classtype = NULL;
	list<Entity*>*class_members = NULL;
	list<Entity*>::iterator i;

	if (INSTANCE_TYPE == kind)
		classtype = ((InstanceType *)baseType)->classtype();
	else if (CLASS_TYPE == kind)
		classtype = ((ClassType *)baseType)->classtype();
	else {
		error->type_error(this->lineno(), "base in field access neither Instance Type nor Class Type\n", fieldName);
		return (new ErrorType());
	}

	while(classtype != objectclass) {
		class_members = classtype->class_members();
		
		for(i = class_members->begin(); i != class_members->end(); i++) {
			if ((FIELD_ENTITY == (*i)->kind()) && (0 == strcmp(fieldName, (*i)->name()))) {
				FieldEntity *field = (FieldEntity *)(*i);

				if ((INSTANCE_TYPE == kind) && (true == (field->static_flag()))) {
					error->type_error(this->lineno(), "static field access by instance type: ", fieldName);
					return (new ErrorType());
				}
				else if ((CLASS_TYPE == kind) && (false == (field->static_flag()))) {
					error->type_error(this->lineno(), "non-static field access by class type: ", fieldName);
					return(new ErrorType());
				}
				else {
					if (PRIVATE == field->visibility_flag()) {
						if( !inConstructor && !inMethod){
							error->type_error(this->lineno(), "private field access outside method/constructor: ", fieldName);
							return(new ErrorType());
						}
						else {
							if (classtype != currClass) {
								error->type_error(this->lineno(), "private field access from a non member function: ", fieldName);
								return(new ErrorType());
							}
							else {
								return field->type();
							}
						}
					}
					else {
						return field->type();
					}
				}
			}
		}
		classtype = classtype->superclass();
	}
	error->type_error(this->lineno(), "field not found in current/superclass: ", fieldName);
	return new ErrorType();
}

// Typeinfer method for MethodInvocation
Type* MethodInvocation::typeinfer() {
  Expression *base = base_;
	char *methodName = name_;
	list<Expression*>*actualArgs = args_;

	ClassEntity *classtype = NULL;
	list<Entity*>*class_members = NULL;
	list<Entity*>::iterator i;

	Type *actArgType = NULL;
	Type *formArgType = NULL;

	Type *baseType = base->typeinfer();
	TypeKind kind = baseType->kind();

	if (INSTANCE_TYPE == kind)
		classtype = ((InstanceType *)baseType)->classtype();
	else if (CLASS_TYPE == kind)
		classtype = ((ClassType *)baseType)->classtype();
	else if (ERROR_TYPE == kind)
		return baseType;
	else {
		error->type_error(this->lineno(), "method not called by Instance or Class Type", methodName);
		return(new ErrorType());
	}

	while(classtype != objectclass) {
		class_members = classtype->class_members();
		
		for(i = class_members->begin(); i != class_members->end(); i++) {
			if ((METHOD_ENTITY == (*i)->kind()) && (0 == strcmp(methodName, (*i)->name()))) {
				MethodEntity *method = (MethodEntity *)(*i);
				list<Entity*>*formalArgs = method->formal_params(); 
				list<Entity*>::iterator iFormArgs = formalArgs->begin();;
				list<Expression*>::iterator iActArgs = actualArgs->begin();

				for (; (iFormArgs != formalArgs->end() && (iActArgs != actualArgs->end())); iFormArgs++, iActArgs++) {
					actArgType = (*iActArgs)->typeinfer();
					formArgType = ((VariableEntity *)(*iFormArgs))->type();

					if (!actArgType->isSubtypeOf(formArgType))
						break;
				}
				
				if ((iFormArgs == formalArgs->end()) && (iActArgs == actualArgs->end())) {
					if ((INSTANCE_TYPE == kind) && (true == method->static_flag())) {
						error->type_error(this->lineno(), "static method invoked: ", methodName);
						return (new ErrorType());
					}
					else if ((CLASS_TYPE == kind) && (false == method->static_flag())) {
						error->type_error(this->lineno(), "non-static method invoked: ", methodName);
						return (new ErrorType());
					}
					else {
						if (PRIVATE == method->visibility_flag()) {
							if( !inConstructor && !inMethod){
								error->type_error(this->lineno(), "private method invoked outside method/constructor: ", methodName);
								return(new ErrorType());
							}
							else {
								if (classtype != currClass) {
									error->type_error(this->lineno(), "private method invoked from a non member function: ", methodName);
									return(new ErrorType());
								}
								else {
									return method->return_type();
								}
							}
						}
						else {
							return method->return_type();
						}
					}
				}
			}
		}
		classtype = classtype->superclass();
	}
	error->type_error(this->lineno(), "Method not found in current/superclass: ", methodName);
	return new ErrorType();
}


// Typeinfer method for UnaryExpression
Type* UnaryExpression::typeinfer() {
	UnaryOperator op = this->unary_operator();
	Expression *arg = this->arg();
	Type *argtype = arg->typeinfer();
	TypeKind argkind = argtype->kind();
	Type *type = NULL;

	if (ERROR_TYPE == argtype->kind())
		return argtype;

	if (NEG == op) {
		if (BOOLEAN_TYPE == argkind) 
			type = new BooleanType();
		else {
			error->type_error(this->lineno(), "Wrong type argument", argtype, new BooleanType());
			type = new ErrorType();
		}
	}
	if (UMINUS == op) {
		if ((INT_TYPE == argkind) || (FLOAT_TYPE == argkind))
			type = new BooleanType();
		else {
			error->type_error(this->lineno(), "Wrong type argument", argtype);
			type = new ErrorType();
		}
	}
	return type;
}



// Typeinfer method for AutoExpression
Type* AutoExpression::typeinfer() {
	Expression *arg = this->arg();
	Type *argtype = arg->typeinfer();
	TypeKind argkind = argtype->kind();

	if (INT_TYPE == argkind)
		return (new IntType());
	else {
		error->type_error(this->lineno(), "Wrong argument type", argtype, new IntType());
		return (new ErrorType());
	}
}


// Typeinfer method for NewArrayInstance: 
Type* NewArrayInstance::typeinfer() {
//	cout<<"in New array Instance"<<endl;
	int dim = this->dimension();
	list<Expression*>*bounds = this->bounds();
	list<Expression*>::iterator i;
	Type *arrayType = NULL;

	for (i = bounds->begin(); i != bounds->end(); i++) {
		Type *type = (*i)->typeinfer();
		TypeKind kind = type->kind();
		if (INT_TYPE != kind){
			error->type_error(this->lineno(), "invalid Array bounds type", type, new IntType());
			return (new ErrorType());
		}
	}
	
	arrayType = this->type();
	for (int count = 1; count <= dim; count++) {
			arrayType = new ArrayType(arrayType);
	}
	return arrayType;
}



// Typeinfer method for NewInstance:
Type* NewInstance::typeinfer() {
	ClassEntity *classEntity = class_entity_;
	list<Expression*>*actualArgs = args_;

	list<Entity*>*class_members = NULL;
	list<Entity*>::iterator i;

	Type *actArgType = NULL;
	Type *formArgType = NULL;

	class_members = classEntity->class_members();
		
	for(i = class_members->begin(); i != class_members->end(); i++) {
		if (CONSTRUCTOR_ENTITY == (*i)->kind()) {
			ConstructorEntity *construct = (ConstructorEntity *)(*i);
			list<Entity*>*formalArgs = construct->formal_params(); 
			list<Entity*>::iterator iFormArgs = formalArgs->begin();;
			list<Expression*>::iterator iActArgs = actualArgs->begin();

			for (; (iFormArgs != formalArgs->end() && (iActArgs != actualArgs->end())); iFormArgs++, iActArgs++) {
				actArgType = (*iActArgs)->typeinfer();
				formArgType = ((VariableEntity *)(*iFormArgs))->type();

				if (ERROR_TYPE == actArgType->kind())
					return actArgType;
				if (ERROR_TYPE == formArgType->kind())
					return formArgType;
				if (!actArgType->isSubtypeOf(formArgType))
					break;
			}
				
			if ((iFormArgs == formalArgs->end()) && (iActArgs == actualArgs->end())) {
				if (PRIVATE == construct->visibility_flag()) {
					error->type_error(this->lineno(), "private constructor called from outside class: ", (char*)classEntity->name());
					return new ErrorType();
				}
				else {
					return new InstanceType(classEntity);
				}
			}
		}
	}
	error->type_error(this->lineno(), "No Matching constructor found in class: ", (char *)classEntity->name());
	return new ErrorType();
}


// Typeinfer method for ThisExpression
Type* ThisExpression::typeinfer() {
	if (!currClass)
		return new ErrorType();

	if (!inMethod && !inConstructor)
		return new ErrorType();
	
	if (inMethod && currMethod->static_flag()) {
		return new ClassType(currClass);
	}
	else {
		return new InstanceType(currClass);
	}
}


// Typeinfer method for SuperExpression
Type* SuperExpression::typeinfer() {
	if (true == currMethod->static_flag()) {
		return new ClassType(currClass->superclass());
	}
	else {
		return new InstanceType(currClass->superclass());
	}
}

// Typeinfer method for IdExpression
Type* IdExpression::typeinfer() {
	cout<<"in id"<<endl;
	Entity *entity = this->id();
	Kind kind = entity->kind();

	if(kind == CLASS_ENTITY) {
		cout<<"1"<<endl;
		return (new ClassType((ClassEntity *)entity));
	}
	else if (kind == VARIABLE_ENTITY) {
		cout<<"2"<<endl;
		return (((VariableEntity *)entity)->type());
	}
	else if (kind == FIELD_ENTITY){
		cout<<"3"<<endl;
		return (((FieldEntity *)entity)->type());
	}
	else
		return (new ErrorType());
}


// Typeinfer method for NullExpression
Type* NullExpression::typeinfer() {
	return (new NullType());
}


// Typeinfer method for BooleanConstant
Type* BooleanConstant::typeinfer() {
	return (new BooleanType());
}

// Typeinfer method for IntegerConstant
Type* IntegerConstant::typeinfer() {
	return (new IntType());
}



// Typeinfer method for FloatConstant
Type* FloatConstant::typeinfer() {
	return (new FloatType());
}


// Typeinfer method for StringConstant
Type* StringConstant::typeinfer() {
	return (new StringType());
}


